using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;

namespace XmlObjects
{
  /// <summary>
  /// The XmlInstanceCreator instruments the creation of each XmlElement, as it is read
  ///   from the Xml reader.
  /// Each Xml element (but not attributes) read from the Xml reader is mapped to
  ///   a XmlInstanceCreator instance, which will eventually create the final class
  ///   instance, setting all its attributes and child elements.
  /// </summary>
  class XmlInstanceCreator
  {
    /// <summary>
    /// Creates a XmlInstanceCreator instance for the given Type, using a specific 
    ///    TypeHelper typeHelperHandler
    /// This is the only public constructor
    /// </summary>
    /// <param name="rootType"></param>
    /// <param name="typeHelperHandler"></param>
    /// <param name="attributes"></param>
    public XmlInstanceCreator(Type rootType, TypeHelperHandler helperHandler, 
      Dictionary<string, string> attributes)
      : this(null, helperHandler, helperHandler.CreateInstance(rootType), attributes)
    {
    }



    /// <summary>
    /// Private constructor, keeps the hierarchy of XmlInstanceCreator
    /// </summary>
    /// <param name="parentNode"></param>
    /// <param name="handler"></param>
    /// <param name="typeHelper"></param>
    /// <param name="attributes">This list is modified directly</param>
    private XmlInstanceCreator(XmlInstanceCreator parentNode, TypeHelperHandler handler,
      TypeHelper typeHelper, Dictionary<string, string> attributes)
    {
      this.parentNode = parentNode;
      this.typeHelper = typeHelper;
      this.elements = new Dictionary<FieldHandler, ArrayList>();
      this.attributes = new Dictionary<FieldHandler, string>();
      this.childs = new ArrayList();
      this.innerText = "";

      //the attribute list could contain the typeattribute
      string attributeName=typeHelper.GetTypeAttributeName();
      if (attributeName != null && attributes.ContainsKey(attributeName))
      {
        string attributeValue = attributes[attributeName];
        this.typeHelper = handler.CreateInstance(attributeValue, typeHelper.XmlType);
        attributes.Remove(attributeName);
      }

      //create all the attributes
      foreach(KeyValuePair<string, string> att in attributes)
        this.attributes[this.typeHelper.GetAttributeHandler(att.Key)] = att.Value;

      //create by default all the variables associated to elements. 
      //This will ensure that, even if no elements are present, the variable will be 
      //  initialized to an empty array
      foreach (FieldHandler fh in this.typeHelper.ElementFieldHandlers)
        this.elements[fh] = new ArrayList();
    }



    /// <summary>
    /// Method called to associate an element to the current XmlInstanceCreator
    /// </summary>
    /// <param name="name">Name of the element to create. The current type
    ///   must define a field with the given name</param>
    /// <param name="attributes"></param>
    /// <returns></returns>
    public XmlInstanceCreator CreateElement(string name, 
      Dictionary<string, string> attributes, TypeHelperHandler handler)
    {
      //GetElementHandler will fail if the name is not valid (it ignores the case)
      FieldHandler fi = typeHelper.GetElementHandler(name);
      XmlInstanceCreator ret = new XmlInstanceCreator(this, handler, fi.TypeHelper, attributes);
      //all elements were already created at startup. elements[fi] is a valid ArrayList
      elements[fi].Add(ret);
      childs.Add(ret);
      return ret;
    }



    /// <summary>
    /// Sets the 'inner text'. This will be mapped to a field in the class, or
    ///   its Parse method will be invoked. As this action depends on whether
    ///   there are attributes or elements defined, no check is performed at
    ///   this stage.
    /// </summary>
    /// <param name="text"></param>
    public void SetInnerText(string text)
    {
      innerText = text;
    }


    /// <summary>
    /// This method is called once per XmlInstanceCreator to inform that all
    ///   attributes/elements are already provided.
    /// This method is responsible to create the final C# instance.
    /// </summary>
    /// <returns></returns>
    public XmlInstanceCreator Completed()
    {
      Debug.Assert(createdElement == null);

      createdElement = typeHelper.CreateInstance(innerText);
      //associate now the attributes
      foreach (KeyValuePair<FieldHandler, string> kv in attributes)
      {
        kv.Key.SetValue(createdElement, kv.Value);
      }
      //associate now the elements.
      foreach (KeyValuePair<FieldHandler, ArrayList> kv in elements)
      {
        //values are arraylists of XmlInstanceCreator. We need the associated XmlElements
        kv.Key.SetValue(createdElement, getChildsAsXmlElements(kv.Value, false));
      }
      //set the childs field, reporting at the same time onthe parents
      typeHelper.SetXmlChilds(createdElement, getChildsAsXmlElements(childs, true));

      return parentNode;
    }



    /// <summary>
    /// Property to access the created XmlElement
    /// </summary>
    public object XmlElement
    {
      get
      {
        Debug.Assert(createdElement != null);
        return createdElement;
      }
    }

    /// <summary>
    /// Returns the array of objects associated to the XmlInstanceCreators 
    ///   specified in the passed list, and sets, optionally, the parent on those
    ///   elements
    /// </summary>
    /// <param name="creators"></param>
    /// <param name="reportParent">set to true if the method SetXmlParent should
    ///   be invoked in the received XmlInstanceCreator instances</param>
    /// <returns></returns>
    private ArrayList getChildsAsXmlElements(ArrayList creators, bool reportParent)
    {
      ArrayList ret = new ArrayList();
      foreach (XmlInstanceCreator creator in creators)
      {
        ret.Add(creator.XmlElement);
        if (reportParent) creator.SetXmlParent(createdElement);
      }
      return ret;
    }


    /// <summary>
    /// Informs to the XmlInstanceCreator on who is the parent of the associated XmlElement
    /// </summary>
    /// <param name="parent"></param>
    public void SetXmlParent(object parent)
    {
      typeHelper.SetXmlParent(createdElement, parent);
    }


    /// <summary>
    /// Invoked to let know the created element that the whole XML file has been read
    /// </summary>
    public void BuildCompleted()
    {
      foreach (XmlInstanceCreator creator in childs)
        creator.BuildCompleted();
      typeHelper.BuildCompleted(createdElement);
    }





    private string innerText;
    private XmlInstanceCreator parentNode;
    private TypeHelper typeHelper;

    /// <summary>
    /// All the childs (XmlInstanceCreator instances)
    /// </summary>
    private ArrayList childs; 

    /// <summary>
    /// Elements associate the fieldHandler of each field defined in the type,
    ///   identified as an element, to an ArrayList of XmlInstanceCreator instances. 
    /// For example, if the field is 'string [] name;', there could be multiple
    ///   name elements, each embedded into a XmlInstanceCreator that would be
    ///   added to this ArrayList
    /// </summary>
    private Dictionary<FieldHandler, ArrayList> elements; 

    /// <summary>
    /// Elements associate the fieldHandler of each field defined in the type,
    ///   identified as an attribute, to the value read from the Xml reader,
    ///   which will always be a string (which can be null)
    /// </summary>
    private Dictionary<FieldHandler, string> attributes; 

    /// <summary>
    /// The c# instance associated to the XmlInstanceCreator.
    /// It is only instantiated when Completed() is called
    /// </summary>
    private object createdElement;

  }
}
